#!/usr/bin/python3 -i
#
# Copyright (c) 2013-2018 The Khronos Group Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os,re,sys
from generator import *

# ValidityOutputGenerator - subclass of OutputGenerator.
# Generates AsciiDoc includes of valid usage information, for reference
# pages and the Vulkan specification. Similar to DocOutputGenerator.
#
# ---- methods ----
# ValidityOutputGenerator(errFile, warnFile, diagFile) - args as for
#   OutputGenerator. Defines additional internal state.
# ---- methods overriding base class ----
# beginFile(genOpts)
# endFile()
# beginFeature(interface, emit)
# endFeature()
# genCmd(cmdinfo)
class ValidityOutputGenerator(OutputGenerator):
    """Generate specified API interfaces in a specific style, such as a C header"""
    def __init__(self,
                 errFile = sys.stderr,
                 warnFile = sys.stderr,
                 diagFile = sys.stdout):
        OutputGenerator.__init__(self, errFile, warnFile, diagFile)

    def beginFile(self, genOpts):
        OutputGenerator.beginFile(self, genOpts)
    def endFile(self):
        OutputGenerator.endFile(self)
    def beginFeature(self, interface, emit):
        # Start processing in superclass
        OutputGenerator.beginFeature(self, interface, emit)
    def endFeature(self):
        # Finish processing in superclass
        OutputGenerator.endFeature(self)

    def makeParameterName(self, name):
        return 'pname:' + name

    def makeStructName(self, name):
        return 'sname:' + name

    def makeBaseTypeName(self, name):
        return 'basetype:' + name

    def makeEnumerationName(self, name):
        return 'elink:' + name

    def makeEnumerantName(self, name):
        return 'ename:' + name

    def makeFLink(self, name):
        return 'flink:' + name

    # Create a unique namespaced Valid Usage anchor name given a
    # blockname - command or structure
    # pname - parameter or member (may be None)
    # category - distinct implicit VU type
    def makeAnchor(self, blockname, pname, category):
        # For debugging
        # return '* '
        if pname != None:
            return '* [[VUID-%s-%s-%s]] ' % (blockname, pname, category)
        else:
            return '* [[VUID-%s-%s]] ' % (blockname, category)

    #
    # Generate an include file
    #
    # directory - subdirectory to put file in
    # basename - base name of the file
    # contents - contents of the file (Asciidoc boilerplate aside)
    def writeInclude(self, directory, basename, validity, threadsafety, commandpropertiesentry, successcodes, errorcodes):
        # Create subdirectory, if needed
        directory = self.genOpts.directory + '/' + directory
        self.makeDir(directory)

        # Create validity file
        filename = directory + '/' + basename + '.txt'
        self.logMsg('diag', '# Generating include file:', filename)

        fp = open(filename, 'w', encoding='utf-8')
        # Asciidoc anchor
        write('// WARNING: DO NOT MODIFY! This file is automatically generated from the vk.xml registry', file=fp)

        # Valid Usage
        if validity is not None:
            write('.Valid Usage (Implicit)', file=fp)
            write('****', file=fp)
            write(validity, file=fp, end='')
            write('****', file=fp)
            write('', file=fp)

        # Host Synchronization
        if threadsafety is not None:
            write('.Host Synchronization', file=fp)
            write('****', file=fp)
            write(threadsafety, file=fp, end='')
            write('****', file=fp)
            write('', file=fp)

        # Command Properties - contained within a block, to avoid table numbering
        if commandpropertiesentry is not None:
            write('.Command Properties', file=fp)
            write('****', file=fp)
            write('[options="header", width="100%"]', file=fp)
            write('|====', file=fp)
            write('|<<VkCommandBufferLevel,Command Buffer Levels>>|<<vkCmdBeginRenderPass,Render Pass Scope>>|<<VkQueueFlagBits,Supported Queue Types>>|<<synchronization-pipeline-stages-types,Pipeline Type>>', file=fp)
            write(commandpropertiesentry, file=fp)
            write('|====', file=fp)
            write('****', file=fp)
            write('', file=fp)

        # Success Codes - contained within a block, to avoid table numbering
        if successcodes is not None or errorcodes is not None:
            write('.Return Codes', file=fp)
            write('****', file=fp)
            if successcodes is not None:
                write('ifndef::doctype-manpage[]', file=fp)
                write('<<fundamentals-successcodes,Success>>::', file=fp)
                write('endif::doctype-manpage[]', file=fp)
                write('ifdef::doctype-manpage[]', file=fp)
                write('On success, this command returns::', file=fp)
                write('endif::doctype-manpage[]', file=fp)
                write(successcodes, file=fp)
            if errorcodes is not None:
                write('ifndef::doctype-manpage[]', file=fp)
                write('<<fundamentals-errorcodes,Failure>>::', file=fp)
                write('endif::doctype-manpage[]', file=fp)
                write('ifdef::doctype-manpage[]', file=fp)
                write('On failure, this command returns::', file=fp)
                write('endif::doctype-manpage[]', file=fp)
                write(errorcodes, file=fp)
            write('****', file=fp)
            write('', file=fp)

        fp.close()

    #
    # Check if the parameter passed in is a pointer
    def paramIsPointer(self, param):
        ispointer = False
        paramtype = param.find('type')
        if paramtype.tail is not None and '*' in paramtype.tail:
            ispointer = True

        return ispointer

    #
    # Check if the parameter passed in is a static array
    def paramIsStaticArray(self, param):
        if param.find('name').tail is not None:
            if param.find('name').tail[0] == '[':
                return True

    #
    # Get the length of a parameter that's been identified as a static array
    def staticArrayLength(self, param):
        paramname = param.find('name')
        paramenumsize = param.find('enum')

        if paramenumsize is not None:
            return paramenumsize.text
        else:
            return paramname.tail[1:-1]

    #
    # Check if the parameter passed in is a pointer to an array
    def paramIsArray(self, param):
        return param.attrib.get('len') is not None

    #
    # Get the parent of a handle object
    def getHandleParent(self, typename):
        types = self.registry.tree.findall("types/type")
        for elem in types:
            if (elem.find("name") is not None and elem.find('name').text == typename) or elem.attrib.get('name') == typename:
                return elem.attrib.get('parent')

        return None

    #
    # Get the ancestors of a handle object
    def getHandleAncestors(self, typename):
        ancestors = []
        current = typename
        while True:
            current = self.getHandleParent(current)
            if current is None:
                return ancestors
            ancestors.append(current)

    #
    # Get the ancestors of a handle object
    def getHandleDispatchableAncestors(self, typename):
        ancestors = []
        current = typename
        while True:
            current = self.getHandleParent(current)
            if current is None:
                return ancestors
            if self.isHandleTypeDispatchable(current):
                ancestors.append(current)

    #
    # Check if a parent object is dispatchable or not
    def isHandleTypeDispatchable(self, handlename):
        handle = self.registry.tree.find("types/type/[name='" + handlename + "'][@category='handle']")
        if handle is not None and handle.find('type').text == 'VK_DEFINE_HANDLE':
            return True
        else:
            return False

    def isHandleOptional(self, param, params):

        # See if the handle is optional
        isOptional = False

        # Simple, if it's optional, return true
        if param.attrib.get('optional') is not None:
            return True

        # If no validity is being generated, it usually means that validity is complex and not absolute, so let's say yes.
        if param.attrib.get('noautovalidity') is not None:
            return True

        # If the parameter is an array and we haven't already returned, find out if any of the len parameters are optional
        if self.paramIsArray(param):
            lengths = param.attrib.get('len').split(',')
            for length in lengths:
                if (length) != 'null-terminated' and (length) != '1':
                    for otherparam in params:
                        if otherparam.find('name').text == length:
                            if otherparam.attrib.get('optional') is not None:
                                return True

        return False
    #
    # Get the category of a type
    def getTypeCategory(self, typename):
        types = self.registry.tree.findall("types/type")
        for elem in types:
            if (elem.find("name") is not None and elem.find('name').text == typename) or elem.attrib.get('name') == typename:
                return elem.attrib.get('category')

    #
    # Make a chunk of text for the end of a parameter if it is an array
    def makeAsciiDocPreChunk(self, blockname, param, params):
        paramname = param.find('name')
        paramtype = param.find('type')

        # General pre-amble. Check optionality and add stuff.
        asciidoc = self.makeAnchor(blockname, paramname.text, 'parameter')

        if self.paramIsStaticArray(param):
            asciidoc += 'Any given element of '

        elif self.paramIsArray(param):
            lengths = param.attrib.get('len').split(',')

            # Find all the parameters that are called out as optional, so we can document that they might be zero, and the array may be ignored
            optionallengths = []
            for length in lengths:
                if (length) != 'null-terminated' and (length) != '1':
                    for otherparam in params:
                        if otherparam.find('name').text == length:
                            if otherparam.attrib.get('optional') is not None:
                                if self.paramIsPointer(otherparam):
                                    optionallengths.append('the value referenced by ' + self.makeParameterName(length))
                                else:
                                    optionallengths.append(self.makeParameterName(length))

            # Document that these arrays may be ignored if any of the length values are 0
            if len(optionallengths) != 0 or param.attrib.get('optional') is not None:
                asciidoc += 'If '


                if len(optionallengths) != 0:
                    if len(optionallengths) == 1:

                        asciidoc += optionallengths[0]
                        asciidoc += ' is '

                    else:
                        asciidoc += ', or '.join(optionallengths)
                        asciidoc += ' are '

                    asciidoc += 'not `0`, '

                if len(optionallengths) != 0 and param.attrib.get('optional') is not None:
                    asciidoc += 'and '

                if param.attrib.get('optional') is not None:
                    asciidoc += self.makeParameterName(paramname.text)
                    asciidoc += ' is not `NULL`, '

        elif param.attrib.get('optional') is not None:
            # Don't generate this stub for bitflags
            if self.getTypeCategory(paramtype.text) != 'bitmask':
                if param.attrib.get('optional').split(',')[0] == 'true':
                    asciidoc += 'If '
                    asciidoc += self.makeParameterName(paramname.text)
                    asciidoc += ' is not '
                    if self.paramIsArray(param) or self.paramIsPointer(param) or self.isHandleTypeDispatchable(paramtype.text):
                        asciidoc += '`NULL`'
                    elif self.getTypeCategory(paramtype.text) == 'handle':
                        asciidoc += 'dlink:VK_NULL_HANDLE'
                    else:
                        asciidoc += '`0`'

                    asciidoc += ', '

        return asciidoc

    #
    # Make the generic asciidoc line chunk portion used for all parameters.
    # May return an empty string if nothing to validate.
    def createValidationLineForParameterIntroChunk(self, blockname, param, params, typetext):
        asciidoc = ''
        paramname = param.find('name')
        paramtype = param.find('type')

        asciidoc += self.makeAsciiDocPreChunk(blockname, param, params)

        asciidoc += self.makeParameterName(paramname.text)
        asciidoc += ' must: be '

        if self.paramIsArray(param):
            # Arrays. These are hard to get right, apparently

            lengths = param.attrib.get('len').split(',')

            if (lengths[0]) == 'null-terminated':
                asciidoc += 'a null-terminated '
            elif (lengths[0]) == '1':
                asciidoc += 'a valid pointer to '
            else:
                asciidoc += 'a valid pointer to an array of '

                # Handle equations, which are currently denoted with latex
                if 'latexmath:' in lengths[0]:
                    asciidoc += lengths[0]
                else:
                    asciidoc += self.makeParameterName(lengths[0])
                asciidoc += ' '

            for length in lengths[1:]:
                if (length) == 'null-terminated': # This should always be the last thing. If it ever isn't for some bizarre reason, then this will need some massaging.
                    asciidoc += 'null-terminated '
                elif (length) == '1':
                    asciidoc += 'valid pointers to '
                else:
                    asciidoc += 'valid pointers to arrays of '
                    # Handle equations, which are currently denoted with latex
                    if 'latexmath:' in length:
                        asciidoc += length
                    else:
                        asciidoc += self.makeParameterName(length)
                    asciidoc += ' '

            # Void pointers don't actually point at anything - remove the word "to"
            if paramtype.text == 'void':
                if lengths[-1] == '1':
                    if len(lengths) > 1:
                        asciidoc = asciidoc[:-5]    # Take care of the extra s added by the post array chunk function. #HACK#
                    else:
                        asciidoc = asciidoc[:-4]
                else:
                    # An array of void values is a byte array.
                    asciidoc += 'byte'

            elif paramtype.text == 'char':
                # A null terminated array of chars is a string
                if lengths[-1] == 'null-terminated':
                    asciidoc += 'UTF-8 string'
                else:
                    # Else it's just a bunch of chars
                    asciidoc += 'char value'
            elif param.text is not None:
                # If a value is "const" that means it won't get modified, so it must be valid going into the function.
                if 'const' in param.text:
                    typecategory = self.getTypeCategory(paramtype.text)
                    if (typecategory != 'struct' and typecategory != 'union' and typecategory != 'basetype' and typecategory is not None) or not self.isStructAlwaysValid(blockname, paramtype.text):
                        asciidoc += 'valid '

            asciidoc += typetext

            # pluralize
            if len(lengths) > 1 or (lengths[0] != '1' and lengths[0] != 'null-terminated'):
                asciidoc += 's'

        elif self.paramIsPointer(param):
            # Handle pointers - which are really special case arrays (i.e. they don't have a length)
            #TODO  should do something here if someone ever uses some intricate comma-separated `optional`
            pointercount = paramtype.tail.count('*')

            # Treat void* as an int
            if paramtype.text == 'void':
                pointercount -= 1

            # Could be multi-level pointers (e.g. ppData - pointer to a pointer). Handle that.
            asciidoc += 'a '
            for i in range(0, pointercount):
                asciidoc += 'valid pointer to a '

            # Handle void* and pointers to it
            if paramtype.text == 'void':
                # If there is only void*, it is just optional int - we don't need any language.
                if pointercount == 0 and param.attrib.get('optional') is not None:
                    return '' # early return
                else:
                    if param.attrib.get('optional').split(',')[pointercount] is not None:
                        # The last void* is just optional int (e.g. to be filled by the impl.)
                        typetext = 'pointer value'


            # If a value is "const" that means it won't get modified, so it must be valid going into the function.
            if param.text is not None and paramtype.text != 'void':
                if 'const' in param.text:
                    asciidoc += 'valid '

            asciidoc += typetext

        else:
            # Non-pointer, non-optional things must be valid
            asciidoc += 'a valid '
            asciidoc += typetext

        if asciidoc != '':
            asciidoc += '\n'

        # Add additional line for non-optional bitmasks
        isOutputParam = self.paramIsPointer(param) and not (param.text is not None and 'const' in param.text)
        if self.getTypeCategory(paramtype.text) == 'bitmask' and not isOutputParam:
            isMandatory = param.attrib.get('optional') is None #TODO does not really handle if someone tries something like optional="true,false"
            if isMandatory:
                asciidoc += self.makeAnchor(blockname, paramname.text, 'requiredbitmask')
                if self.paramIsArray(param):
                    asciidoc += 'Each element of '
                asciidoc += 'pname:'
                asciidoc += paramname.text
                asciidoc += ' must: not be `0`'
                asciidoc += '\n'

        return asciidoc

    def makeAsciiDocLineForParameter(self, blockname, param, params, typetext):
        if param.attrib.get('noautovalidity') is not None:
            return ''
        asciidoc  = self.createValidationLineForParameterIntroChunk(blockname, param, params, typetext)

        return asciidoc

    # Check if a structure is always considered valid (i.e. there are no rules to its acceptance)
    def isStructAlwaysValid(self, blockname, structname):

        struct = self.registry.tree.find("types/type[@name='" + structname + "']")

        if struct.attrib.get('returnedonly') is not None:
            return True

        params = struct.findall('member')

        for param in params:
            paramname = param.find('name')
            paramtype = param.find('type')
            typecategory = self.getTypeCategory(paramtype.text)

            if paramname.text == 'pNext':
                return False

            if paramname.text == 'sType':
                return False

            if param.attrib.get('noautovalidity') is not None:
                return False

            if paramtype.text == 'void' or paramtype.text == 'char' or self.paramIsArray(param) or self.paramIsPointer(param):
                if self.makeAsciiDocLineForParameter(blockname, param, params, '') != '':
                    return False
            elif typecategory == 'handle' or typecategory == 'enum' or typecategory == 'bitmask':
                return False
            elif typecategory == 'struct' or typecategory == 'union':
                if self.isStructAlwaysValid(blockname, paramtype.text) is False:
                    return False

        return True

    #
    # Make an entire asciidoc line for a given parameter
    def createValidationLineForParameter(self, blockname, param, params, typecategory):
        asciidoc = ''
        paramname = param.find('name')
        paramtype = param.find('type')

        if paramtype.text == 'void' or paramtype.text == 'char':
            # Chars and void are special cases - needs care inside the generator functions
            # A null-terminated char array is a string, else it's chars.
            # An array of void values is a byte array, a void pointer is just a pointer to nothing in particular
            asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, '')
        elif typecategory == 'bitmask':
            bitsname = paramtype.text.replace('Flags', 'FlagBits')
            if self.registry.tree.find("enums[@name='" + bitsname + "']") is None:
                asciidoc += self.makeAnchor(blockname, paramname.text, 'zerobitmask')
                asciidoc += self.makeParameterName(paramname.text)
                asciidoc += ' must: be `0`'
                asciidoc += '\n'
            else:
                if self.paramIsArray(param):
                    #
                    if param.text is not None and 'const' in param.text:
                        asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, 'combinations of ' + self.makeEnumerationName(bitsname) + ' value')
                    else:
                        asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, self.makeEnumerationName(paramtype.text) + ' value')
                elif self.paramIsPointer(param):
                    if param.text is not None and 'const' in param.text:
                        asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, 'combination of ' + self.makeEnumerationName(bitsname) + ' values')
                    else:
                        asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, self.makeEnumerationName(paramtype.text) + ' value')
                else:
                    asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, 'combination of ' + self.makeEnumerationName(bitsname) + ' values')
        elif typecategory == 'handle':
            asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, self.makeStructName(paramtype.text) + ' handle')
        elif typecategory == 'enum':
            asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, self.makeEnumerationName(paramtype.text) + ' value')
        elif typecategory == 'struct':
            if (self.paramIsArray(param) or self.paramIsPointer(param)) or not self.isStructAlwaysValid(blockname, paramtype.text):
                asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, self.makeStructName(paramtype.text) + ' structure')
        elif typecategory == 'union':
            if (self.paramIsArray(param) or self.paramIsPointer(param)) or not self.isStructAlwaysValid(blockname, paramtype.text):
                asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, self.makeStructName(paramtype.text) + ' union')
        elif self.paramIsArray(param) or self.paramIsPointer(param):
            asciidoc += self.makeAsciiDocLineForParameter(blockname, param, params, self.makeBaseTypeName(paramtype.text) + ' value')

        return asciidoc

    #
    # Make an asciidoc validity entry for a handle's parent object
    def makeAsciiDocHandleParent(self, blockname, param, params):
        asciidoc = ''
        paramname = param.find('name')
        paramtype = param.find('type')

        # Deal with handle parents
        handleparent = self.getHandleParent(paramtype.text)
        if handleparent is not None:
            parentreference = None
            for otherparam in params:
                if otherparam.find('type').text == handleparent:
                    parentreference = otherparam.find('name').text
            if parentreference is not None:
                asciidoc += self.makeAnchor(blockname, paramname.text, 'parent')

                if self.isHandleOptional(param, params):
                    if self.paramIsArray(param):
                        asciidoc += 'Each element of '
                        asciidoc += self.makeParameterName(paramname.text)
                        asciidoc += ' that is a valid handle'
                    else:
                        asciidoc += 'If '
                        asciidoc += self.makeParameterName(paramname.text)
                        asciidoc += ' is a valid handle, it'
                else:
                    if self.paramIsArray(param):
                        asciidoc += 'Each element of '
                    asciidoc += self.makeParameterName(paramname.text)
                asciidoc += ' must: have been created, allocated, or retrieved from '
                asciidoc += self.makeParameterName(parentreference)

                asciidoc += '\n'
        return asciidoc

    #
    # Make an asciidoc validity entry for a common ancestors between handles
    def makeAsciiDocHandlesCommonAncestor(self, blockname, handles, params):
        asciidoc = ''

        if len(handles) > 1:
            ancestormap = {}
            anyoptional = False

            # Find all the ancestors
            for param in handles:
                paramname = param.find('name')
                paramtype = param.find('type')

                if not self.paramIsPointer(param) or (param.text is not None and 'const' in param.text):
                    ancestors = self.getHandleDispatchableAncestors(paramtype.text)

                    ancestormap[param] = ancestors

                    anyoptional |= self.isHandleOptional(param, params)

            # Remove redundant ancestor lists
            for param in handles:
                paramname = param.find('name')
                paramtype = param.find('type')

                removals = []
                for ancestors in ancestormap.items():
                    if paramtype.text in ancestors[1]:
                        removals.append(ancestors[0])

                if removals != []:
                    for removal in removals:
                        del(ancestormap[removal])

            # Intersect

            if len(ancestormap.values()) > 1:
                current = list(ancestormap.values())[0]
                for ancestors in list(ancestormap.values())[1:]:
                    current = [val for val in current if val in ancestors]

                if len(current) > 0:
                    commonancestor = current[0]

                    if len(ancestormap.keys()) > 1:

                        asciidoc += self.makeAnchor(blockname, None, 'commonparent')

                        parametertexts = []
                        for param in ancestormap.keys():
                            paramname = param.find('name')
                            parametertext = self.makeParameterName(paramname.text)
                            if self.paramIsArray(param):
                                parametertext = 'the elements of ' + parametertext
                            parametertexts.append(parametertext)

                        parametertexts.sort()

                        if len(parametertexts) > 2:
                            asciidoc += 'Each of '
                        else:
                            asciidoc += 'Both of '

                        asciidoc += ", ".join(parametertexts[:-1])
                        asciidoc += ', and '
                        asciidoc += parametertexts[-1]
                        if anyoptional is True:
                            asciidoc += ' that are valid handles'
                        asciidoc += ' must: have been created, allocated, or retrieved from the same '
                        asciidoc += self.makeStructName(commonancestor)
                        asciidoc += '\n'

        return asciidoc

    #
    # Generate an asciidoc validity line for the sType value of a struct
    def makeStructureType(self, blockname, param):
        paramname = param.find('name')
        paramtype = param.find('type')

        asciidoc = self.makeAnchor(blockname, paramname.text, 'sType')
        asciidoc += self.makeParameterName(paramname.text)
        asciidoc += ' must: be '

        values = param.attrib.get('values')
        if values:
            # Extract each enumerant value. They could be validated in the
            # same fashion as validextensionstructs in
            # makeStructureExtensionPointer, although that's not relevant in
            # the current extension struct model.
            valuelist = [ self.makeEnumerantName(v) for v in values.split(',') ]
        else:
            structuretype = ''
            for elem in re.findall(r'(([A-Z][a-z]+)|([A-Z][A-Z]+))', blockname):
                if elem[0] == 'Vk':
                    structuretype += 'VK_STRUCTURE_TYPE_'
                else:
                    structuretype += elem[0].upper()
                    structuretype += '_'
            valuelist = [ self.makeEnumerantName(structuretype[:-1]) ]

        if len(valuelist) > 0:
            if len(valuelist) == 1:
                asciidoc += valuelist[0]
            else:
                asciidoc += (', ').join(valuelist[:-1]) + ', or ' + valuelist[-1]
        asciidoc += '\n'

        return asciidoc

    #
    # Generate an asciidoc validity line for the pNext value of a struct
    def makeStructureExtensionPointer(self, blockname, param):
        paramname = param.find('name')
        paramtype = param.find('type')

        if (param.attrib.get('validextensionstructs') is not None):
            self.logMsg('warn', blockname, 'validextensionstructs is deprecated/removed', '\n')

        asciidoc = self.makeAnchor(blockname, paramname.text, 'pNext')
        validextensionstructs = self.registry.validextensionstructs.get(blockname)
        extensionstructs = []

        if validextensionstructs is not None:
            # Check each structure name and skip it if not required by the
            # generator. This allows tagging extension structs in the XML
            # that are only included in validity when needed for the spec
            # being targeted.
            for struct in validextensionstructs:
                # Unpleasantly breaks encapsulation. Should be a method in the registry class
                type = self.registry.lookupElementInfo(struct, self.registry.typedict)
                if (type.required):
                    extensionstructs.append('slink:' + struct)
                else:
                    self.logMsg('diag', 'makeStructureExtensionPointer: struct', struct, 'IS NOT required')

        if len(extensionstructs) == 0:
            asciidoc += self.makeParameterName(paramname.text)
            asciidoc += ' must: be `NULL`'
        elif len(extensionstructs) == 1:
            asciidoc += self.makeParameterName(paramname.text)
            asciidoc += ' must: be `NULL` or a pointer to a valid instance of '
            asciidoc += extensionstructs[0]
        else:
            asciidoc += 'Each '
            asciidoc += self.makeParameterName(paramname.text)
            asciidoc += ' member of any structure (including this one) in the pname:pNext chain must: be either `NULL` or a pointer to a valid instance of '

            if len(extensionstructs) == 2:
                asciidoc += extensionstructs[0] + ' or ' + extensionstructs[1]
            else:
                asciidoc += (', ').join(extensionstructs[:-1]) + ', or ' + extensionstructs[-1]
            asciidoc += '\n'

            asciidoc += self.makeAnchor(blockname, 'sType', 'unique')
            asciidoc += 'Each pname:sType member in the pname:pNext chain must: be unique'

        asciidoc += '\n'

        return asciidoc

    #
    # Generate all the valid usage information for a given struct that's only ever filled out by the implementation other than sType and pNext
    def makeValidUsageStatementsReturnedOnly(self, cmd, blockname, params):
        # Start the asciidoc block for this
        asciidoc = ''

        for param in params:
            paramname = param.find('name')
            paramtype = param.find('type')

            # Valid usage ID tags (VUID) are generated for various
            # conditions based on the name of the block (structure or
            # command), name of the element (member or parameter), and type
            # of VU statement.

            # Get the type's category
            typecategory = self.getTypeCategory(paramtype.text)

            if param.attrib.get('noautovalidity') is None:
                # Generate language to independently validate a parameter
                if paramtype.text == 'VkStructureType' and paramname.text == 'sType':
                    asciidoc += self.makeStructureType(blockname, param)
                elif paramname.text == 'pNext' and paramtype.text == 'void' and cmd.attrib.get('structextends') is None:
                    asciidoc += self.makeStructureExtensionPointer(blockname, param)

        # In case there's nothing to report, return None
        if asciidoc == '':
            return None

        return asciidoc

    #
    # Generate all the valid usage information for a given struct or command
    def makeValidUsageStatements(self, cmd, blockname, params):
        # Start the asciidoc block for this
        asciidoc = ''

        handles = []
        anyparentedhandlesoptional = False
        parentdictionary = {}
        arraylengths = set()
        for param in params:
            paramname = param.find('name')
            paramtype = param.find('type')

            # Valid usage ID tags (VUID) are generated for various
            # conditions based on the name of the block (structure or
            # command), name of the element (member or parameter), and type
            # of VU statement.

            # Get the type's category
            typecategory = self.getTypeCategory(paramtype.text)

            if param.attrib.get('noautovalidity') is None:
                # Generate language to independently validate a parameter
                if paramtype.text == 'VkStructureType' and paramname.text == 'sType':
                    asciidoc += self.makeStructureType(blockname, param)
                elif paramtype.text == 'void' and paramname.text == 'pNext':
                    if cmd.attrib.get('structextends') is None:
                        asciidoc += self.makeStructureExtensionPointer(blockname, param)
                else:
                    asciidoc += self.createValidationLineForParameter(blockname, param, params, typecategory)

            # Ensure that any parenting is properly validated, and list that a handle was found
            if typecategory == 'handle':
                handles.append(param)

            # Get the array length for this parameter
            arraylength = param.attrib.get('len')
            if arraylength is not None:
                for onelength in arraylength.split(','):
                    arraylengths.add(onelength)

        # For any vkQueue* functions, there might be queue type data
        if 'vkQueue' in blockname:
            # The queue type must be valid
            queuetypes = cmd.attrib.get('queues')
            if queuetypes is not None:
                queuebits = []
                for queuetype in re.findall(r'([^,]+)', queuetypes):
                    queuebits.append(queuetype.replace('_',' '))

                asciidoc += self.makeAnchor(blockname, None, 'queuetype')
                asciidoc += 'The pname:queue must: support '
                if len(queuebits) == 1:
                    asciidoc += queuebits[0]
                else:
                    asciidoc += (', ').join(queuebits[:-1])
                    asciidoc += ', or '
                    asciidoc += queuebits[-1]
                asciidoc += ' operations'
                asciidoc += '\n'

        if 'vkCmd' in blockname:
            # The commandBuffer parameter must be being recorded
            asciidoc += self.makeAnchor(blockname, 'commandBuffer', 'recording')
            asciidoc += 'pname:commandBuffer must: be in the <<commandbuffers-lifecycle, recording state>>'
            asciidoc += '\n'

            #
            # Start of valid queue type validation - command pool must have been
            # allocated against a queue with at least one of the valid queue types
            asciidoc += self.makeAnchor(blockname, 'commandBuffer', 'cmdpool')

            #
            # This test for vkCmdFillBuffer is a hack, since we have no path
            # to conditionally have queues enabled or disabled by an extension.
            # As the VU stuff is all moving out (hopefully soon), this hack solves the issue for now
            if blockname == 'vkCmdFillBuffer':
                if 'VK_KHR_maintenance1' in self.registry.requiredextensions:
                    asciidoc += 'The sname:VkCommandPool that pname:commandBuffer was allocated from must: support transfer, graphics or compute operations\n'
                else:
                    asciidoc += 'The sname:VkCommandPool that pname:commandBuffer was allocated from must: support graphics or compute operations\n'
            else:
                # The queue type must be valid
                queuetypes = cmd.attrib.get('queues')
                queuebits = []
                for queuetype in re.findall(r'([^,]+)', queuetypes):
                    queuebits.append(queuetype.replace('_',' '))

                asciidoc += 'The sname:VkCommandPool that pname:commandBuffer was allocated from must: support '
                if len(queuebits) == 1:
                    asciidoc += queuebits[0]
                else:
                    asciidoc += (', ').join(queuebits[:-1])
                    asciidoc += ', or '
                    asciidoc += queuebits[-1]
                asciidoc += ' operations'
                asciidoc += '\n'

            # Must be called inside/outside a renderpass appropriately
            renderpass = cmd.attrib.get('renderpass')

            if renderpass != 'both':
                asciidoc += self.makeAnchor(blockname, None, 'renderpass')
                asciidoc += 'This command must: only be called '
                asciidoc += renderpass
                asciidoc += ' of a render pass instance'
                asciidoc += '\n'

            # Must be in the right level command buffer
            cmdbufferlevel = cmd.attrib.get('cmdbufferlevel')

            if cmdbufferlevel != 'primary,secondary':
                asciidoc += self.makeAnchor(blockname, None, 'bufferlevel')
                asciidoc += 'pname:commandBuffer must: be a '
                asciidoc += cmdbufferlevel
                asciidoc += ' sname:VkCommandBuffer'
                asciidoc += '\n'

        # Any non-optional arraylengths should specify they must be greater than 0
        for param in params:
            paramname = param.find('name')

            for arraylength in arraylengths:
                if paramname.text == arraylength and param.attrib.get('optional') is None:
                    # Get all the array dependencies
                    arrays = cmd.findall("param/[@len='" + arraylength + "'][@optional='true']")

                    # Get all the optional array dependencies, including those not generating validity for some reason
                    optionalarrays = cmd.findall("param/[@len='" + arraylength + "'][@optional='true']")
                    optionalarrays.extend(cmd.findall("param/[@len='" + arraylength + "'][@noautovalidity='true']"))

                    # If arraylength can ever be not a legal part of an
                    # asciidoc anchor name, this will need to be altered.
                    asciidoc += self.makeAnchor(blockname, arraylength, 'arraylength')

                    # Allow lengths to be arbitrary if all their dependents are optional
                    if len(optionalarrays) == len(arrays) and len(optionalarrays) != 0:
                        asciidoc += 'If '
                        if len(optionalarrays) > 1:
                            asciidoc += 'any of '

                        for array in optionalarrays[:-1]:
                            asciidoc += self.makeParameterName(optionalarrays.find('name').text)
                            asciidoc += ', '

                        if len(optionalarrays) > 1:
                            asciidoc += 'and '
                            asciidoc += self.makeParameterName(optionalarrays[-1].find('name').text)
                            asciidoc += ' are '
                        else:
                            asciidoc += self.makeParameterName(optionalarrays[-1].find('name').text)
                            asciidoc += ' is '

                        asciidoc += 'not `NULL`, '

                        if self.paramIsPointer(param):
                            asciidoc += 'the value referenced by '

                    elif self.paramIsPointer(param):
                        asciidoc += 'The value referenced by '

                    asciidoc += self.makeParameterName(arraylength)
                    asciidoc += ' must: be greater than `0`'
                    asciidoc += '\n'

        # Find the parents of all objects referenced in this command
        for param in handles:
            paramtype = param.find('type')
            # Don't detect a parent for return values!
            if not self.paramIsPointer(param) or (param.text is not None and 'const' in param.text):

                parent = self.getHandleParent(paramtype.text)

                if parent is not None:
                    asciidoc += self.makeAsciiDocHandleParent(blockname, param, params)

        # Find the common ancestor of all objects referenced in this command
        asciidoc += self.makeAsciiDocHandlesCommonAncestor(blockname, handles, params)

        # In case there's nothing to report, return None
        if asciidoc == '':
            return None
        # Delimit the asciidoc block
        return asciidoc

    def makeThreadSafetyBlock(self, cmd, paramtext):
        """Generate C function pointer typedef for <command> Element"""
        paramdecl = ''

        # Find and add any parameters that are thread unsafe
        explicitexternsyncparams = cmd.findall(paramtext + "[@externsync]")
        if (explicitexternsyncparams is not None):
            for param in explicitexternsyncparams:
                externsyncattribs = param.attrib.get('externsync')
                paramname = param.find('name')
                for externsyncattrib in externsyncattribs.split(','):
                    paramdecl += '* '
                    paramdecl += 'Host access to '
                    if externsyncattrib == 'true':
                        if self.paramIsArray(param):
                            paramdecl += 'each member of ' + self.makeParameterName(paramname.text)
                        elif self.paramIsPointer(param):
                            paramdecl += 'the object referenced by ' + self.makeParameterName(paramname.text)
                        else:
                            paramdecl += self.makeParameterName(paramname.text)
                    else:
                        paramdecl += 'pname:'
                        paramdecl += externsyncattrib
                    paramdecl += ' must: be externally synchronized\n'

        # For any vkCmd* functions, the command pool is externally synchronized
        if cmd.find('proto/name') is not None and 'vkCmd' in cmd.find('proto/name').text:
            paramdecl += '* '
            paramdecl += 'Host access to the sname:VkCommandPool that pname:commandBuffer was allocated from must: be externally synchronized'
            paramdecl += '\n'

        # Find and add any "implicit" parameters that are thread unsafe
        implicitexternsyncparams = cmd.find('implicitexternsyncparams')
        if (implicitexternsyncparams is not None):
            for elem in implicitexternsyncparams:
                paramdecl += '* '
                paramdecl += 'Host access to '
                paramdecl += elem.text
                paramdecl += ' must: be externally synchronized\n'

        if (paramdecl == ''):
            return None
        else:
            return paramdecl

    def makeCommandPropertiesTableEntry(self, cmd, name):

        if 'vkCmd' in name:
            # Must be called inside/outside a renderpass appropriately
            cmdbufferlevel = cmd.attrib.get('cmdbufferlevel')
            cmdbufferlevel = (' + \n').join(cmdbufferlevel.title().split(','))

            renderpass = cmd.attrib.get('renderpass')
            renderpass = renderpass.capitalize()

            #
            # This test for vkCmdFillBuffer is a hack, since we have no path
            # to conditionally have queues enabled or disabled by an extension.
            # As the VU stuff is all moving out (hopefully soon), this hack solves the issue for now
            if name == 'vkCmdFillBuffer':
                if 'VK_KHR_maintenance1' in self.registry.requiredextensions:
                    queues = 'Transfer + \nGraphics + \nCompute'
                else:
                    queues = 'Graphics + \nCompute'
            else:
                queues = cmd.attrib.get('queues')
                queues = (' + \n').join(queues.title().split(','))

            pipeline = cmd.attrib.get('pipeline')
            if pipeline:
                pipeline = pipeline.capitalize()
            else:
                pipeline = ''

            return '|' + cmdbufferlevel + '|' + renderpass + '|' + queues + '|' + pipeline
        elif 'vkQueue' in name:
            # Must be called inside/outside a renderpass appropriately

            queues = cmd.attrib.get('queues')
            if queues is None:
                queues = 'Any'
            else:
                queues = (' + \n').join(queues.upper().split(','))

            return '|-|-|' + queues + '|-'

        return None

    # Check each enumerant name in the enums list and remove it if not
    # required by the generator. This allows specifying success and error
    # codes for extensions that are only included in validity when needed
    # for the spec being targeted.
    def findRequiredEnums(self, enums):
        out = []
        for enum in enums:
            # Unpleasantly breaks encapsulation. Should be a method in the registry class
            ei = self.registry.lookupElementInfo(enum, self.registry.enumdict)
            if (ei == None):
                self.logMsg('warn', 'findRequiredEnums: enum', enum, 'is in an attribute list but is not in the registry')
            elif (ei.required):
                out.append(enum)
            else:
                self.logMsg('diag', 'findRequiredEnums: enum', enum, 'IS NOT required, skipping')
        return out

    def makeSuccessCodes(self, cmd, name):
        successcodes = cmd.attrib.get('successcodes')
        if successcodes is not None:
            successcodes = self.findRequiredEnums(successcodes.split(','))
            if len(successcodes) > 0:
                return '* ename:' + '\n* ename:'.join(successcodes)
        return None

    def makeErrorCodes(self, cmd, name):
        errorcodes = cmd.attrib.get('errorcodes')
        if errorcodes is not None:
            errorcodes = self.findRequiredEnums(errorcodes.split(','))
            if len(errorcodes) > 0:
                return '* ename:' + '\n* ename:'.join(errorcodes)
        return None

    #
    # Command generation
    def genCmd(self, cmdinfo, name, alias):
        OutputGenerator.genCmd(self, cmdinfo, name, alias)

        # @@@ (Jon) something needs to be done here to handle aliases, probably

        #
        # Get all the parameters
        params = cmdinfo.elem.findall('param')

        validity = self.makeValidUsageStatements(cmdinfo.elem, name, params)
        threadsafety = self.makeThreadSafetyBlock(cmdinfo.elem, 'param')
        commandpropertiesentry = self.makeCommandPropertiesTableEntry(cmdinfo.elem, name)
        successcodes = self.makeSuccessCodes(cmdinfo.elem, name)
        errorcodes = self.makeErrorCodes(cmdinfo.elem, name)

        self.writeInclude('protos', name, validity, threadsafety, commandpropertiesentry, successcodes, errorcodes)

    #
    # Struct Generation
    def genStruct(self, typeinfo, typename, alias):
        OutputGenerator.genStruct(self, typeinfo, typename, alias)

        # @@@ (Jon) something needs to be done here to handle aliases, probably

        # Anything that's only ever returned can't be set by the user, so shouldn't have any validity information.
        if typeinfo.elem.attrib.get('returnedonly') is None:
            params = typeinfo.elem.findall('member')

            validity = self.makeValidUsageStatements(typeinfo.elem, typename, params)
            threadsafety = self.makeThreadSafetyBlock(typeinfo.elem, 'member')

            self.writeInclude('structs', typename, validity, threadsafety, None, None, None)
        else:
            # Need to generate sType and pNext validation
            params = typeinfo.elem.findall('member')

            validity = self.makeValidUsageStatementsReturnedOnly(typeinfo.elem, typename, params)

            self.writeInclude('structs', typename, validity, None, None, None, None)

    #
    # Group (e.g. C "enum" type) generation.
    # For the validity generator, this just tags individual enumerants
    # as required or not.
    def genGroup(self, groupinfo, groupName, alias):
        OutputGenerator.genGroup(self, groupinfo, groupName, alias)

        # @@@ (Jon) something needs to be done here to handle aliases, probably

        groupElem = groupinfo.elem

        # Loop over the nested 'enum' tags. Keep track of the minimum and
        # maximum numeric values, if they can be determined; but only for
        # core API enumerants, not extension enumerants. This is inferred
        # by looking for 'extends' attributes.
        for elem in groupElem.findall('enum'):
            name = elem.get('name')
            ei = self.registry.lookupElementInfo(name, self.registry.enumdict)

            # Tag enumerant as required or not
            ei.required = self.isEnumRequired(elem)
    #
    # Type Generation
    def genType(self, typeinfo, typename, alias):
        OutputGenerator.genType(self, typeinfo, typename, alias)

        # @@@ (Jon) something needs to be done here to handle aliases, probably

        category = typeinfo.elem.get('category')
        if (category == 'struct' or category == 'union'):
            self.genStruct(typeinfo, typename, alias)
